//
//  sstTcp.m
//  OpensslTest
//
//  Created by jiahua on 16/3/30.
//  Copyright © 2016年 secsmarts. All rights reserved.
//

#import "sstTcp.h"
#import "sstNSDataFormat.h"
#import "sstNSStringFormat.h"
#import "sstComstruct.h"
#import "KeyManage.h"
#import "DeviceManager.h"
#import "SstDevice.h"
#import "Define.h"
@interface sstTcp ()

//
// TCP协议发包过程中包的编号
//
@property(nonatomic)NSInteger num;
@property(copy, nonatomic)NSString* host;

@end

@implementation sstTcp

-(void)myInit{
    self.num = -1;
}

-(void)myInitWithIP:(NSString*)host{
    self.num = -1;
    self.host = host;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark connection
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

-(BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError *__autoreleasing *)errPtr{
    [self myInit];
    return [super connectToAddress:remoteAddr error:errPtr];
}

-(BOOL)connectToAddress:(NSData *)remoteAddr viaInterface:(NSString *)interface withTimeout:(NSTimeInterval)timeout error:(NSError *__autoreleasing *)errPtr{
    [self myInit];
    return [super connectToAddress:remoteAddr viaInterface:interface withTimeout:timeout error:errPtr];
}

-(BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError *__autoreleasing *)errPtr{
    [self myInit];
    return [super connectToAddress:remoteAddr withTimeout:timeout error:errPtr];
}

-(BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError *__autoreleasing *)errPtr{
    [self myInitWithIP:host];
    return [super connectToHost:host onPort:port error:errPtr];
}

-(BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port viaInterface:(NSString *)interface withTimeout:(NSTimeInterval)timeout error:(NSError *__autoreleasing *)errPtr{
    [self myInitWithIP:host];
    return [super connectToHost:host onPort:port viaInterface:interface withTimeout:timeout error:errPtr];
}

-(BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port withTimeout:(NSTimeInterval)timeout error:(NSError *__autoreleasing *)errPtr{
    [self myInitWithIP:host];
    return [super connectToHost:host onPort:port withTimeout:timeout error:errPtr];
}

- (void)disconnect
{
    SstDevice *device = [[DeviceManager Instance] getSstDeviceByIp: [self host]];
    [device clearTcpKey];
    
    [super disconnect];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark WriteMessage
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 *  写数据，把数据进行加密装包
 *
 *  @param data 要传输的明文数据
 *  @param timeout
 *  @param tag
 */
-(void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag{
    u8 *bufferData = [sstNSDataFormat NSData2Char:data];
    u8 cmdTag;//用来表示发送数据时，包头cmd的值。0协商秘钥发送，6发送业务数据
    u8 outDataBuffer[SST_SEC_SIZE] = {0};
    int outDataLen = 0;
    NSString *ip = [self host];
    int iRtn = SST_OK;
    
    NSLog(@"发送TCP【目的地】IP:%@-->data:%@", ip, [sstNSDataFormat NSData2NSString:data]);
    SstDevice *device = [[DeviceManager Instance] getSstDeviceByIp: ip];
    if (device.isLegacy)// 旧家电，直接返回
        return [super writeData:data withTimeout:timeout tag:tag];
    
    //1、首先看是否已经协商完秘钥能够通信，每次只在发送数据前进行秘钥协商
        //0、是否为协商秘钥发送动作，发送秘钥为自己生成的R3
    cmdTag = CMD_DATA;
    if(!device.isTcpHandShaked)
    {
        // 1. send r1 encode by hash(uuid + password + mac)
        iRtn = [self handShakeWithDevice: device];
    }
    //还有一种可能是tag==NOENCRYPT_TAG的情况，也就是下面给排除的情况
    //协商秘钥的条件
   if(device.isTcpHandShaked == NO)
   {
        //发送数据时，如果没有在keyManage中找到秘钥，就返回nil空消息。静默状态
        //用来判断用户是否实现了此代理方法
        SEL selector = @selector(sstTcpError:withTag:);
        __strong id theDelegate = self.tcpErrorDelegate;
        if ([theDelegate respondsToSelector:selector]){
            [self.tcpErrorDelegate sstTcpError:self withTag: iRtn];
        }
        [super writeData:nil withTimeout:timeout tag:tag];
        return;//秘钥协商失败，直接返回。
    }
    else
    {
        SEL selector = @selector(sstTcpInfoNotify:withInfo:);
        __strong id theDelegate = self.tcpInfoNotifyDelegate;
        if ([theDelegate respondsToSelector:selector]){
            [self.tcpInfoNotifyDelegate sstTcpInfoNotify:self withInfo:[sstNSStringFormat Char2NSString: device.k1]];
        }

    }
    int num = (int)self.num;
    int resultTag = myConstructSecPacketRaw(bufferData, (int)data.length, device.tcpKey, UDP_KEY_ID_LEN, &num, 1, cmdTag, outDataBuffer, &outDataLen);
    if(resultTag < 0){
        //如果加密装包有问题，那么返回nil空消息。静默状态
        NSLog(@"加密装包出错 -->【退出】");
        //用来判断用户是否实现了此代理方法
        SEL selector = @selector(sstTcpError:withTag:);
        __strong id theDelegate = self.tcpErrorDelegate;
        if ([theDelegate respondsToSelector:selector]){
            [self.tcpErrorDelegate sstTcpError:self withTag:(NSInteger)(-1*resultTag)];
        }
        [super writeData:nil withTimeout:timeout tag:tag];
    }
    self.num = num;
    NSData* outData = [NSData dataWithBytes: (const char *)outDataBuffer length:outDataLen];
    NSLog(@"TCP加密包发送成功");
    [super writeData:outData withTimeout:timeout tag:tag];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark ReadMessage
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 *  读取数据，把数据进行拆包解密。
 *
 *  @param result 传入的数据
 *
 *  @return 返回拆包解密后的明文数据
 */
- (NSData *)handleReadData:(NSData *)result{
    u8* bufferData = (u8*)[result bytes];
    int bufferDataLen = (int)[result length];
    u8 outData[SST_SEC_SIZE] = {0};
    int outDataLen = 0;
    int iRtn = SST_OK;
    
    NSString *ip = [self host];
    NSLog(@"TCP数据【来源】IP --> %@", ip);
    //tcp接收消息的两种情况：
    //1）接收正常消息，使用协商完的秘钥进行解密
    //2）接收协商秘钥的回复，我需要使用key1进行解密
    SstDevice *device = [[DeviceManager Instance] getSstDeviceByIp: ip];
    SstSecHead* Sp2 = (SstSecHead*)bufferData;
    if (device.isLegacy)// 旧家电，直接返回
        return  result;
    
    switch (Sp2->Cmd) {
        case DISCUSS_CMD_RECV:
        {
            iRtn = [self handleHandBackPackageWithDevice:result device: device];
            if(iRtn != SST_OK)
            {
                [self callErrorDelegageWithErrorTag: iRtn];
            }
            return nil;
         }
        case CMD_ERROR:
            [device clearTcpKey];
            iRtn = SST_DEVICE_DECODE_ERROR;
            break;
        default:
            break;
    }
    if(!device.isTcpHandShaked){
        //由于某种情况无法获得秘钥的情况下，那么也是返回nil空消息。静默状态
        NSLog(@"无法找到秘钥解密收到数据 -->【退出】");
        [self callErrorDelegageWithErrorTag: iRtn];
        return nil;
    }
    else
    {
        SEL selector = @selector(sstTcpInfoNotify:withInfo:);
        __strong id theDelegate = self.tcpInfoNotifyDelegate;
        if ([theDelegate respondsToSelector:selector]){
            [self.tcpInfoNotifyDelegate sstTcpInfoNotify:self withInfo:[sstNSStringFormat Byte2HexNSString: device.k1 withLength:LAN_KEY_LEN]];
        }
    }
    //    LogHex("self_sstTCP的key", initKey, strlen((const char *)initKey));
    int num = (int)self.num;
    iRtn = myDestructSecPacketRaw(bufferData, bufferDataLen, device.tcpKey, HASH_DIGEST_LEN, &num, outData, &outDataLen);
    if(iRtn != SST_OK){
       
        [self callErrorDelegageWithErrorTag: iRtn];
        //如果拆包解密出问题，那么返回nil空消息。静默状态
        [device clearTcpKey];
        return nil;
    }
    self.num = num;
    result = [sstNSDataFormat Char2NSData:outData withLen:outDataLen];
    NSLog(@"self_sstTCP接收到的数据为---> %@", [sstNSDataFormat NSData2NSString:result]);
    return result;
}

-(void)callErrorDelegageWithErrorTag: (NSInteger) iRtn
{
    //用来判断用户是否实现了此代理方法
    SEL selector = @selector(sstTcpError:withTag:);
    __strong id theDelegate = self.tcpErrorDelegate;
    if ([theDelegate respondsToSelector:selector]){
        [self.tcpErrorDelegate sstTcpError:self withTag:(NSInteger)iRtn];
    }
}
/**
 *  读取数据，把数据进行拆包解密。
 *
 *  @param result 传入的数据
 *
 *  @return 返回拆包解密后的明文数据
 */
-(int)handShakeWithDevice: (SstDevice *)device
{
    int iRtn = SST_OK;
    //2、通过获取的家电的状态确定秘钥协商的方式
    //必须为阻塞式的,bufferData为要发送的数据key1
    u8 * r3 = nil;
    int r3len = 0;
    int outDataLen = 0;
    int inDataLen = 0;
    u8 * inData = nil;
    
    if (device == nil)
    {
        iRtn = SST_NO_DEVICE_INFO_ERROR;
        return iRtn;
    }
    if(device.hasGetR3)
    {//家电已经获取R3，直接发送token.
        inData = device.hexToken;
        inDataLen = TOKEN_LENGTH;
    }
    else
    {//家电未获取R3，根据SSID协商秘钥。
        inData = device.k1;
        r3 = device.ssidKey;
        r3len = HASH_DIGEST_LEN;
        inDataLen = LICENSE_INIT_KEY_LEN;
    }
    u8 *outU8 = (u8*)malloc(SST_HEAD_SIZE + LICENSE_INIT_KEY_LEN + SST_HASH_SIZE);
    getHelloToDevicePackage(inData, inDataLen, r3, r3len, outU8, &outDataLen);
    
    //就使用当前Tcp进行协商秘钥
    [super writeData: [sstNSDataFormat Char2NSData:outU8 withLen:outDataLen]
         withTimeout:TIME_OUT tag: DISCUSS_SEND_TAG];
    // 接收秘钥协商
    [self readDataWithTimeout: -1 tag:DISCUSS_SEND_TAG];
    
    //这里需要一直等待直到接收到了对应Ip返回的数据，或者超时退出
    NSTimeInterval preTime = [[NSDate date] timeIntervalSinceNow];//NSTimeInterval是以秒为单位的
     //如果没有拿到key2并且还没有超时，那么就一直死循环
    while(!device.isTcpHandShaked && ([[NSDate date] timeIntervalSinceNow]-preTime) < TIME_OUT){
        //一直循环查看
        [NSThread sleepForTimeInterval:0.02];
     }
    
    free(outU8);
    return iRtn;
}

//家电端返回秘钥协商请求，在此函数解析出K2
-(int)handleHandBackPackageWithDevice:(NSData *)data device:(SstDevice *)device
{
    int iRtn = SST_OK;
    //2、通过获取的家电的状态确定秘钥协商的方式
    //必须为阻塞式的,bufferData为要发送的数据key1
    u8 * k1 = device.k1;
    int k1len = 0;

    u8 *r2buf = (u8*)malloc(SST_HEAD_SIZE + LICENSE_INIT_KEY_LEN + SST_HASH_SIZE + UDP_KEY_ID_LEN);
    int outDataLen = 0;
    int len = getDeviceAnswer((const u8 *)[data bytes], (int)data.length, k1, k1len, r2buf, &outDataLen, SST_CMD_TCP_DATA);
    if (len == SST_OK)
    {
        device.tcpK2 = r2buf;
    }
    free(r2buf);
    
    return iRtn;
}

@end
